#!/usr/bin/env python3

import os
import sys
import cx_Oracle
import hashlib
import psycopg2
import pdb
import logging
import click
import logging
import yaml
from threading import Thread
from operator import itemgetter

ora_username='eular_dev'
ora_password='Paas0426'
#ora_username='paastest'
#ora_password='Paas1015'
#ora_conn_str='192.168.100.113:1521/nlpass01'
ora_conn_str='127.0.0.1:1521/nlpass01'

pg_username='postgres'
pg_password='Post123'
pg_database='postgres'
pg_host='172.32.148.156'
pg_port='1921'
pg_schema='public'

#如果dry_run=True只生成SQL，不查询也不计算md5
dry_run=False
debug_print=False
calc_clob=True
calc_blob=True

# 创建日志目录，可以通过环境变量MD5LOGDIR指定
log_dir=os.environ.get('MD5LOGDIR', '.')
if not os.path.exists(log_dir):
  os.makedirs(log_dir)
log_file = log_dir + '/out.log'

# create and truncate log
with open(log_file, 'w') as f:
    pass

logging.basicConfig(level=logging.INFO,
                    format='%(levelname)s[%(asctime)s]:%(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',
                    filename=log_file,
                    filemode='a+')

def debug_print_row(row, clob_cnt):
    if debug_print:
        row_str = ''
        for i in range(0, clob_cnt):
            if row[i] != None:
                row_str += row[i]
        print(row_str)

def xor_bytea_str(a, b):
    xored = bytearray(b'00000000000000000000000000000000')
    for i in range(0, len(a)):
        xored[i] = a[i]^ord(b[i])
    return xored

def number_type_format_string(precision, scale):
    format_string = ''
    if precision == None:
        precision = 38
    if scale == None or scale <= 0:
        return None
    elif scale >= precision:
        format_string += '0D'
        format_string += '9'*scale
    else:
        dn = precision - scale
        format_string += '9'*(dn-1)
        format_string += '0D'
        format_string += '9'*scale
    return format_string

# the handler enables code to retrieve clob or blob value like ordinary columns
def output_type_handler(cursorsor, name, default_type, size, precision, scale):
    if default_type == cx_Oracle.DB_TYPE_CLOB:
        return cursorsor.var(cx_Oracle.DB_TYPE_LONG, arraysize=cursorsor.arraysize)
    if default_type == cx_Oracle.DB_TYPE_BLOB:
        return cursorsor.var(cx_Oracle.DB_TYPE_LONG_RAW, arraysize=cursorsor.arraysize)

def ora_table_md5(tablename, tableowner, connection, first_n):
    cursor=connection.cursor()
    cursor.prefetchrows = 500
    cursor.arraysize = 100
    select_list_string = ''
    blob_cnt = 0
    select_list_blob = ''
    clob_cnt = 0
    select_list_clob = ''
    sel_tab_str = ''
    is_to_clob = False
    record_cnt = 0
    tabname = tablename.upper()
    owner = tableowner.upper()
    sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_SCALE FROM ALL_TAB_COLUMNS WHERE TABLE_NAME= :tab AND OWNER = :owner ORDER BY COLUMN_NAME"
    cursor.execute(sql, tab = tabname, owner = owner)
    res_cols_info = cursor.fetchall()
    if len(res_cols_info) == 0:
        logging.info('{} : {}'.format(tabname, "TABLE DOES NOT EXIT!!!"))
        return "TABLE DOES NOT EXIT!!!"
    # 对于Oracle以中文为列名的表，Oracle和PG对列名排序无法一致，还是需要利用统一的Python规则排一次序
    # 所有列的信息，按照Python规则统一排序
    sorted_res_cols_info = sorted(res_cols_info, key=itemgetter(0), reverse=False)
    def build_sel_str(res):
        # 引用外面定义的变量
        nonlocal select_list_string
        nonlocal blob_cnt
        nonlocal select_list_blob
        nonlocal clob_cnt
        nonlocal select_list_clob
        nonlocal sel_tab_str
        select_list_string = ''
        blob_cnt = 0
        select_list_blob = ''
        clob_cnt = 0
        select_list_clob = ''
        sel_tab_str = ''
        for row in res:
            #row[0] COLUMN_NAME
            #row[1] DATA_TYPE
            #row[2] DATA_PRECISION
            #row[3] DATA_SCALE
            # \"{}\" -- in case oracle column names use pg keywords
            col_str = ''
            if row[1] in ('VARCHAR2', 'VARCHAR', 'CHAR', 'NCHAR', 'NVARCHAR2'):
                col_str = '\"{}\"'.format(row[0])
            elif row[1] in ('NUMBER','FLOAT'):
                format_string = number_type_format_string(row[2], row[3])
                if format_string == None:
                    col_str = 'to_char(\"{}\")'.format(row[0])
                else:
                    col_str = 'to_char(\"{}\", \'{}\')'.format(row[0], format_string)
            elif row[1] == 'DATE':
                col_str = 'to_char(\"{}\", \'YYYY-MM-DD HH24:MI:SS\')'.format(row[0])
            elif row[1].find('TIMESTAMP') == 0:
                if row[3] == 0:
                    # timestamp(0) only have second resolution.
                    col_str = 'to_char(\"{}\", \'YYYY-MM-DD HH24:MI:SS\')'.format(row[0])
                else:
                    col_str = 'to_char(\"{}\", \'YYYY-MM-DD HH24:MI:SSxFF6\')'.format(row[0])
            elif row[1] == 'BLOB':
                if len(select_list_blob) > 0:
                    select_list_blob += ','
                select_list_blob += '\"{}\"'.format(row[0])
                blob_cnt += 1
                continue
            elif row[1] in ('CLOB','LONG'):
                if len(select_list_clob) > 0:
                    select_list_clob += ','
                select_list_clob += '\"{}\"'.format(row[0])
                clob_cnt += 1
                continue
            else:
                raise Exception('Error: Unknown type {}.'.format(row[1]))
    
            if len(select_list_string) > 0:
                select_list_string += '||'
            else:
                # 第一列转为CLOB，之后的列也是CLOB
                if is_to_clob:
                    col_str = 'TO_CLOB(' + col_str + ')'
            select_list_string += col_str
        # end for
        # 增加CLOB列
        if len(select_list_string) > 0:
            # 其它非CLOB列组成一个字符串列，clob_cnt至少是1
            clob_cnt += 1
            if len(select_list_clob) > 0:
                select_list_string += ',' + select_list_clob
        else:
            select_list_string = select_list_clob
        # 增加BLOB列
        if len(select_list_string) > 0:
            if len(select_list_blob) > 0:
                select_list_string += ',' + select_list_blob
        else:
            select_list_string = select_list_blob
        # 拼出不含ORDER BY的SQL
        sel_tab_str = 'SELECT {} FROM {}'.format(select_list_string, tabname)

    build_sel_str(sorted_res_cols_info)

    order_by_list = ''
    sql = """SELECT COLUMN_NAME FROM USER_CONS_COLUMNS UCC
                              JOIN USER_CONSTRAINTS UC ON UCC.CONSTRAINT_NAME = UC.CONSTRAINT_NAME
                              WHERE UC.CONSTRAINT_TYPE = 'P' AND UC.TABLE_NAME = :tab
                              ORDER BY UCC.POSITION"""

    cursor.execute(sql, tab = tabname)
    res_orderby = cursor.fetchall()

    def build_sel_str_with_orderby(res):
        nonlocal order_by_list
        nonlocal sel_tab_str
        order_by_list = ''
        for row in res:
            if len(order_by_list) != 0:
                order_by_list += ','
            order_by_list += row[0]
        if len(order_by_list) > 0:
            sel_tab_str += ' ORDER BY ' + order_by_list
        logging.info('{}'.format(sel_tab_str))

    build_sel_str_with_orderby(res_orderby)

    # if has primary key, order by primary key
    if len(order_by_list) > 0:
        # 除了cursor.execute时会有ORA-01489，
        # cursor.fetchmany时也会有ORA-01489，
        # 即执行到一半后报错，这可能与Oracle的配置有关，
        # 但我不知道是什么配置，所以这里任何一种情况出现，
        # 异常将从函数抛出，被外层捕获后，
        # 以TO_CLOB重新构造select list，再次调用函数。
        def checksum_pk():
            nonlocal sel_tab_str
            nonlocal cursor
            global dry_run
            nonlocal clob_cnt
            nonlocal blob_cnt
            global calc_clob
            nonlocal tabname
            nonlocal record_cnt
            nonlocal first_n            
            record_cnt = 0
            cursor.execute(sel_tab_str)
            if dry_run:
                return ''
            m = hashlib.md5()
            # 一次读取cursor.arraysize行，不能用fetchall，以防OOM
            res = cursor.fetchmany()
            while res:
                for row in res:
                    debug_print_row(row, clob_cnt)
                    if calc_clob:
                        for i in range(0, clob_cnt):
                            if row[i] != None:
                                m.update(row[i].encode("utf-8"))
                    if calc_blob:
                        for i in range(clob_cnt, clob_cnt + blob_cnt):
                            if row[i] != None:
                                m.update(row[i])
                    record_cnt += 1
                    if record_cnt == first_n:
                        cursor.close()
                        md5_str = m.hexdigest().upper()
                        logging.info('{} : {}'.format(tabname, md5_str))
                        return md5_str
                # 一次读取cursor.arraysize行，不能用fetchall，以防OOM
                res = cursor.fetchmany()
            cursor.close()
            md5_str = m.hexdigest().upper()
            logging.info('{} : {}'.format(tabname, md5_str))
            return md5_str

        try:
            return checksum_pk()
        except cx_Oracle.DatabaseError as exc:
            error, = exc.args
            # 所有字段的组合太长，解决办法是在第一个字段加TO_CLOB
            # 第一列转为CLOB，||之后的列也是CLOB
            if error.code == 1489:
                logging.info('Result of String Concatenation is Too Long -- Using TO_CLOB().')
                is_to_clob = True
                build_sel_str(sorted_res_cols_info)
                build_sel_str_with_orderby(res_orderby)
                return checksum_pk()
            else:
                raise exc

    else:
        # if does not have primary key, 
        # calculate md5 for each row and xor md5s.
        def checksum_no_pk():
            nonlocal sel_tab_str
            nonlocal cursor
            global dry_run
            nonlocal clob_cnt
            nonlocal blob_cnt
            global calc_clob
            nonlocal tabname
            nonlocal record_cnt
            nonlocal first_n
            record_cnt = 0
            cursor.execute(sel_tab_str)
            if dry_run:
                return ''
            # 一次读取cursor.arraysize行，不能用fetchall，以防OOM
            res = cursor.fetchmany()
            # 32个0
            rt = bytearray(b'00000000000000000000000000000000')
            while res:
                for row in res:
                    debug_print_row(row, clob_cnt)
                    m = hashlib.md5()
                    if calc_clob:
                        for i in range(0, clob_cnt):
                            if row[i] != None:
                                m.update(row[i].encode("utf-8"))
                    if calc_blob:
                        for i in range(clob_cnt, clob_cnt + blob_cnt):
                            if row[i] != None:
                                m.update(row[i])
                    # 不同顺序，但是内容相同的记录之间异或，最终结果相同
                    rt = xor_bytea_str(rt, m.hexdigest())
                    record_cnt += 1
                    if record_cnt == first_n:
                        cursor.close()
                        xor_str = ''.join(format(x, '02X') for x in rt)
                        logging.info('{} : {}'.format(tabname, xor_str))
                        return xor_str
                # 一次读取cursor.arraysize行，不能用fetchall，以防OOM
                res = cursor.fetchmany()
            cursor.close()
            xor_str = ''.join(format(x, '02X') for x in rt)
            logging.info('{} : {}'.format(tabname, xor_str))
            return xor_str

        try:
            return checksum_no_pk()
        except cx_Oracle.DatabaseError as exc:
            error, = exc.args
            # 所有字段的组合太长，在第一个字段加TO_CLOB
            if error.code == 1489:
                logging.info('Result of String Concatenation is Too Long -- Using TO_CLOB().')
                is_to_clob = True
                build_sel_str(sorted_res_cols_info)
                build_sel_str_with_orderby(res_orderby)
                return checksum_no_pk()
            else:
                raise exc


def pg_table_md5(schemaname, tablename, connection, first_n):
    select_list_string = ''
    select_list_bytea = ''
    select_list_text = ''
    bytea_cnt = 0
    text_cnt = 0
    record_cnt = 0
    tabname = tablename.lower()
    schname = schemaname.lower()
    cursor=connection.cursor()
    cursor.arraysize = 100
    # 获取列信息时，要转换成大写后排序，且按照与Oracle相同的字符串排序规则，即binary。
    cursor.execute("""select upper(column_name), data_type, numeric_precision, numeric_scale, datetime_precision 
                     from INFORMATION_SCHEMA.COLUMNS 
                     where table_name = %s and table_schema = %s order by upper(column_name) collate "C" """, (tabname, schname,))
    res_cols_info = cursor.fetchall()
    if len(res_cols_info) == 0:
        logging.info('{} : {}'.format(tabname, "TABLE DOES NOT EXIT!!!"))
        return "TABLE DOES NOT EXIT!!!"
    # 所有列的信息，按照Python的规则，按照列名统一排序，列名已转换为大写
    sorted_res_cols_info = sorted(res_cols_info, key=itemgetter(0), reverse=False)
    for row in sorted_res_cols_info:
        # row[0] column_name
        # row[1] data_type
        # row[2] numeric_precision
        # row[3] numeric_scale
        # row[4] datetime_precision
        # \"{}\" -- in case oracle column names use pg keywords
        col_str = ''
        # 再转换为小写，PG元数据存储列名，默认小写
        colname = row[0].lower()
        if row[1] in ('character varying', 'character'):
            col_str = '\"{}\"'.format(colname)
        elif row[1] in ('integer', 'bigint', 'smallint', 'numeric', 'double precision'):
            format_string = number_type_format_string(row[2], row[3])
            if format_string == None:
                col_str = '\"{}\"'.format(colname, format_string)
            else:
                col_str = 'to_char(\"{}\", \'{}\')'.format(colname, format_string)
        elif row[1] == 'date':
            col_str = 'to_char(\"{}\", \'YYYY-MM-DD\')'.format(colname)
        elif row[1].find('timestamp') == 0:
            if row[4] == 0:
                # for Oracle date and timestamp(0)
                col_str = 'to_char(\"{}\", \'YYYY-MM-DD HH24:MI:SS\')'.format(colname)
            else:
                col_str = 'to_char(\"{}\", \'YYYY-MM-DD HH24:MI:SS.US\')'.format(colname)
        elif row[1] == 'bytea':
            if len(select_list_bytea) > 0:
                select_list_bytea += ','
            select_list_bytea += '\"{}\"'.format(colname)
            bytea_cnt += 1
            continue
        elif row[1] == 'text':
            if len(select_list_text) > 0:
                select_list_text += ','
            select_list_text += '\"{}\"'.format(colname)
            text_cnt += 1
            continue
        else:
            return 'unkown type {}.'.format(row[1])

        if len(select_list_string) > 0:
            select_list_string += ','
        select_list_string += col_str
    # end for

    if len(select_list_string) > 0:
        text_cnt += 1
        select_list_string = 'concat(' + select_list_string + ')'

    if len(select_list_string) > 0:
        if len(select_list_text) > 0:
            select_list_string += ',' + select_list_text
    else:
        select_list_string = select_list_text

    if len(select_list_string) > 0:
        if len(select_list_bytea) > 0:
            select_list_string += ',' + select_list_bytea
    else:
        select_list_string = select_list_bytea
            
    sel_tab_str = 'SELECT {} FROM {}.{}'.format(select_list_string, schname, tabname)
 
    # build order by clause
    order_by_string = ''
    cursor.execute("""select kcu.column_name from information_schema.key_column_usage kcu
                          join information_schema.table_constraints tc
                          on kcu.constraint_name = tc.constraint_name
                          where tc.table_name = %s and tc.table_schema = %s and tc.constraint_type = 'PRIMARY KEY' 
                          order by kcu.ordinal_position""", (tabname, schname,));
    res = cursor.fetchall()
    for row in res:
        if len(order_by_string) != 0:
            order_by_string += ','
        order_by_string += row[0]
        # 对于字符串类型的主键，设置排序规则与Oracle相同，即binary
        type_string = [v[1] for i, v in enumerate(sorted_res_cols_info) if v[0] == row[0].upper()][0]
        if type_string in ('character varying', 'character'):
            order_by_string += ' COLLATE "C"'

    if len(order_by_string) > 0:
        sel_tab_str += ' ORDER BY ' + order_by_string

    logging.info('{}'.format(sel_tab_str))
    # for comparing pk-ordered able
    if len(order_by_string) > 0:
        cursor.execute(sel_tab_str)
        if dry_run:
           return ''
        m = hashlib.md5()
        res = cursor.fetchmany()
        while res:
            for row in res:
                debug_print_row(row, text_cnt)
                if calc_clob:
                    for i in range(0, text_cnt):
                        if row[i] != None:
                            m.update(row[i].encode("utf-8"))
                if calc_blob:
                    for i in range(text_cnt, text_cnt + bytea_cnt):
                        if row[i] != None:
                            m.update(bytes(row[i]))
                record_cnt += 1
                if record_cnt == first_n:
                    cursor.close()
                    md5_str = m.hexdigest().upper()
                    logging.info('{} : {}'.format(tabname, md5_str))
                    return md5_str 
            res = cursor.fetchmany()
        cursor.close()
        md5_str = m.hexdigest().upper()
        logging.info('{} : {}'.format(tabname, md5_str))
        return md5_str
    else:
        # for comparing un-ordered table
        cursor.execute(sel_tab_str)
        if dry_run:
           return ''
        res = cursor.fetchmany()
        # 32个0
        rt = bytearray(b'00000000000000000000000000000000')
        while res:
            for row in res:
                debug_print_row(row, text_cnt)
                m = hashlib.md5()
                if calc_clob:
                    for i in range(0, text_cnt):
                        if row[i] != None:
                            m.update(row[i].encode("utf-8"))
                if calc_blob:
                    for i in range(text_cnt, text_cnt + bytea_cnt):
                        if row[i] != None:
                            m.update(row[i])
                # 不同顺序，但是内容相同的记录之间异或，最终结果相同
                # 与之前的结果做异或计算
                rt = xor_bytea_str(rt, m.hexdigest())
                record_cnt += 1
                if record_cnt == first_n:
                    cursor.close()
                    xor_str = ''.join(format(x, '02X') for x in rt)
                    logging.info('{} : {}'.format(tabname, xor_str))
                    return xor_str
            res = cursor.fetchmany()
        cursor.close()
        xor_str = ''.join(format(x, '02X') for x in rt)
        logging.info('{} : {}'.format(tabname, xor_str))
        return xor_str


def ora_table_cnt(tabname, owner, connection, first_n):
    cursor=connection.cursor()
    cursor.execute('SELECT COUNT(1) FROM {}'.format(tabname))
    row = cursor.fetchone()
    return row[0]
    
def pg_table_cnt(schname, tabname, connection, first_n):
    cursor=connection.cursor()
    cursor.execute('SELECT COUNT(1) FROM {}'.format(tabname))
    row = cursor.fetchone()
    return row[0]

def ora_multi_table(table_process_func, username, password, conn_str, first_n, *tables):
    retdict = {}
    connection=cx_Oracle.connect(username, password, conn_str)
    connection.outputtypehandler = output_type_handler
    for tab in tables:
        retdict[tab] = table_process_func(tab, username, connection, first_n)
    connection.close()
    return retdict

def pg_multi_table(table_process_func, database, user, password, host, port, schema, first_n, *tables):
    retdict = {}
    connection = psycopg2.connect(database=database, user=user, password=password, host=host, port=port)
    for tab in tables:
        retdict[tab] = table_process_func(schema, tab, connection, first_n)
    connection.close()
    return retdict



class MyThread(Thread):
    def __init__(self, func, args):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args

    def run(self):
        self.result = self.func(*self.args)

    def get_result(self):
        try:
            return self.result
        except Exception:
            return None


@click.group(context_settings=dict(help_option_names=['-h', '--help']))
def main():
    pass


# One chunk is a list of tables
# returns a list of chunks
def get_chunks(table_list, input_file, parallel):
    tablist = []
    if table_list:
        tablist = table_list.split(',')
    elif input_file:
        with open(input_file, 'r', encoding="utf-8") as f:
            while True:
                tabname = f.readline().strip()
                if len(tabname) > 0:
                    tablist.append(tabname)
                else:
                    break;
    else:
        raise Exception('Error: No input tables.')

    chunk_size = int(len(tablist) / parallel)
    if chunk_size <= 0:
        raise Exception('Error: table number: {}, parallel: {}.'.format(len(tablist), parallel))

    chunked_list = [tablist[i:i+chunk_size] for i in range(0, len(tablist), chunk_size)]
    return chunked_list


def process_chunks(chunked_list, process_func, args_tuple, hasTableListArg, output_file):
    thd_list = []
    idx = 0
    for tabs in chunked_list:
        thd = MyThread(process_func, args=args_tuple + tuple(tabs))
        thd.start()
        thd_list.append(thd)
        idx += 1
        print('Session {}: process {}.'.format(idx, tabs))

    # wait for threads to finish in order
    if hasTableListArg:
        idx = 0
        for t in thd_list:
            t.join()
            idx += 1
            print('Session {}: Finished.'.format(idx))
            res = t.get_result()
            for k in res:
                print('{} : {}'.format(k, res[k]))
    elif output_file:
        with open(output_file, 'w') as f:
            idx = 0
            for t in thd_list:
                t.join()
                idx += 1
                print('Session {}: Finished.'.format(idx))
                res = t.get_result()
                for k in res:
                    f.write('{} : {}\n'.format(k, res[k]))
    else:
        raise Exception('Error: No output file.')


@main.command('ora', help='Compute md5 of oracle tables.', no_args_is_help=True)
@click.option('--parallel', '-P', default=1, help='Parallel threads')
@click.option('--output-file', '-o',default='ora-md5.txt', help='Output file')
@click.option('--input-file', '-i', help='Intput file')
@click.option('--table-list', '-t', help='Input table list e.g. -t "tb1,tb2,tb3"')
@click.option('--first-n', '-n', default=-1, help='Compare first n record')
def ora(parallel, input_file, output_file, table_list, first_n):
    # 1 thread per chunk, total thread num = parallel
    chunks = get_chunks(table_list, input_file, parallel)
    process_chunks(chunks, ora_multi_table, (ora_table_md5, ora_username, ora_password, ora_conn_str, first_n), table_list!=None, output_file)


@main.command('pg', help='Compute md5 of postgresql tables.', no_args_is_help=True)
@click.option('--parallel', '-P', default=1, help='Parallel threads')
@click.option('--output-file', '-o',default='pg-md5.txt', help='Output file')
@click.option('--input-file', '-i', help='Intput file')
@click.option('--table-list', '-t', help='Input table list e.g. -t "tb1,tb2,tb3"')
@click.option('--first-n', '-n', default=-1, help='Compare first n record')
def pg(parallel, output_file, input_file, table_list, first_n):
    # 1 thread per chunk, total thread num = parallel
    chunks = get_chunks(table_list, input_file, parallel)
    process_chunks(chunks, pg_multi_table, (pg_table_md5, pg_database, pg_username, pg_password, pg_host, pg_port, pg_schema, first_n), table_list!=None, output_file)


@main.command('ora-cnt', help='Compute row count of oracle tables.', no_args_is_help=True)
@click.option('--parallel', '-P', default=1, help='Parallel threads')
@click.option('--output-file', '-o',default='ora-cnt.txt', help='Output file')
@click.option('--input-file', '-i', help='Intput file')
@click.option('--table-list', '-t', help='Input table list e.g. -t "tb1,tb2,tb3"')
def ora(parallel, input_file, output_file, table_list):
    # 1 thread per chunk, total thread num = parallel
    chunks = get_chunks(table_list, input_file, parallel)
    process_chunks(chunks, ora_multi_table, (ora_table_cnt, ora_username, ora_password, ora_conn_str, -1), table_list!=None, output_file)


@main.command('pg-cnt', help='Compute row count of postgresql tables.', no_args_is_help=True)
@click.option('--parallel', '-P', default=1, help='Parallel threads')
@click.option('--output-file', '-o',default='pg-cnt.txt', help='Output file')
@click.option('--input-file', '-i', help='Intput file')
@click.option('--table-list', '-t', help='Input table list e.g. -t "tb1,tb2,tb3"')
def pg(parallel, output_file, input_file, table_list):
    # 1 thread per chunk, total thread num = parallel
    chunks = get_chunks(table_list, input_file, parallel)
    process_chunks(chunks, pg_multi_table, (pg_table_cnt, pg_database, pg_username, pg_password, pg_host, pg_port, pg_schema, -1), table_list!=None, output_file)


if __name__ == '__main__':
    main(None)

